package com.idata.service.services;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.idata.common.constant.RedisKeyConstant;
import com.idata.model.dto.KGGraphExploreNodeDTO;
import com.idata.model.response.GraphExploreResponse;
import com.idata.model.response.GraphExploreResponse.Edge;
import org.apache.commons.collections4.ListUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class GraphExploreService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String nodeHashFieldPrefix = "nodes:";

    private static final String edgeHashFieldPrefix = "edge:";

    /**
     * 先过滤
     */
    public GraphExploreResponse<KGGraphExploreNodeDTO> filterExplore(String partyId, GraphExploreResponse<KGGraphExploreNodeDTO> response) {
        String redisKey = this.formatRedisKey(partyId);
        List<KGGraphExploreNodeDTO> originalNodes = response.getNodes();
        // 筛选出现过的节点和自身节点
        List<String> filteredIdCards = filterNodes(redisKey, originalNodes.stream().map(KGGraphExploreNodeDTO::getId).collect(Collectors.toList()));
        filteredIdCards = ListUtils.removeAll(filteredIdCards, ListUtil.toList(partyId));
        Map<String, KGGraphExploreNodeDTO> originalNodeMap = originalNodes.stream().collect(Collectors.toMap(KGGraphExploreNodeDTO::getId,
                Function.identity(),
                (oldValue, newValue) -> newValue));
        List<KGGraphExploreNodeDTO> filteredNodes = filteredIdCards.stream().map(originalNodeMap::get).collect(Collectors.toList());

        List<Edge> originalEdges = response.getEdges();
        Map<String, List<Edge>> edgeUniqIdMap = originalEdges.stream().collect(Collectors.groupingBy(edge -> genEdgeUniqId(edge.getSrcNode(), edge.getDstNode())));
        List<String> filteredEdgesUniqId = filterEdges(redisKey, new ArrayList<>(edgeUniqIdMap.keySet()));
        List<Edge> filteredEdges = edgeUniqIdMap.entrySet().stream().filter(entry -> filteredEdgesUniqId.contains(entry.getKey())).map(Entry::getValue).flatMap(List::stream).collect(Collectors.toList());
        return new GraphExploreResponse<KGGraphExploreNodeDTO>().setNodes(filteredNodes).setEdges(filteredEdges);
    }

    /**
     * 初始化探索
     */
    public void saveNodesAndEdgesForConditionExplore(String partyId, GraphExploreResponse<KGGraphExploreNodeDTO> response) {
        saveNodesAndEdges(partyId, partyId, response);
    }

    /**
     * 后探索
     */
    public void saveNodesAndEdges(String partyId, String exploreId, GraphExploreResponse<KGGraphExploreNodeDTO> response) {
        String redisKey = this.formatRedisKey(partyId);
        // 探索exploreId时，记录所有的ids
        List<String> exploreIds = response.getNodes().stream().map(KGGraphExploreNodeDTO::getId).collect(Collectors.toList());
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        hashOps.put(redisKey, nodeHashFieldPrefix + exploreId, exploreIds);

        // 探索exploreId时，记录所有的边
        List<String> edgesUniqIds = response.getEdges().stream().map(edge -> genEdgeUniqId(edge.getSrcNode(), edge.getDstNode())).collect(Collectors.toList());
        hashOps.put(redisKey, edgeHashFieldPrefix + exploreId, edgesUniqIds);
    }


    /**
     * 清空整个图
     */
    public void flushGraph(String partyId) {
        String redisKey = this.formatRedisKey(partyId);
        redisTemplate.delete(redisKey);
    }

    /**
     * 回退探索节点
     */
    public void deleteExploreId(String partyId, String exploreId) {
        String redisKey = this.formatRedisKey(partyId);
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        hashOps.delete(redisKey,  edgeHashFieldPrefix + exploreId);
        hashOps.delete(redisKey,  nodeHashFieldPrefix + exploreId);
    }


    /**
     * 给定一批探索出的数据，根据 partyId 之前探索过的节点，过滤已经存在的节点
     */
    private List<String> filterNodes(String redisKeyForPartyId, List<String> originalNodes) {
        if (CollectionUtil.isEmpty(originalNodes)) {
            return new ArrayList<>();
        }
        List<String> existsNodes = this.getFields(redisKeyForPartyId, nodeHashFieldPrefix).stream()
                .map(nodeField -> Optional.ofNullable(this.getValues(redisKeyForPartyId, nodeField)).orElse(new ArrayList<>()))
                .flatMap(List::stream).collect(Collectors.toList());
        return ListUtils.removeAll(originalNodes, existsNodes);
    }

    /**
     * 给定一批探索出的数据，根据 partyId 之前探索过的节点，过滤已经存在的节点
     */
    private List<String> filterEdges(String redisKeyForPartyId, List<String> originalEdges) {
        if (CollectionUtil.isEmpty(originalEdges)) {
            return new ArrayList<>();
        }
        List<String> existsEdges = this.getFields(redisKeyForPartyId, edgeHashFieldPrefix).stream()
                .map(nodeField -> Optional.ofNullable(this.getValues(redisKeyForPartyId, nodeField)).orElse(new ArrayList<>()))
                .flatMap(List::stream).collect(Collectors.toList());
        return ListUtils.removeAll(originalEdges, existsEdges);
    }

    private List<String> getValues(String redisKeyForPartyId, String exploreIdWithPrefix) {
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        return (List<String>) hashOps.get(redisKeyForPartyId, exploreIdWithPrefix);
    }

    private static String genEdgeUniqId(String exploreId, String node) {
        if (StrUtil.compare(exploreId, node, true) < 0) {
            return exploreId + "-" + node;
        } else {
            return node + "-" + exploreId;
        }
    }


    public List<String> getNodesFields(String redisKeyForPartyId) {
        return getFields(redisKeyForPartyId, nodeHashFieldPrefix);
    }

    public List<String> getEdgeFields(String redisKeyForPartyId) {
        return getFields(redisKeyForPartyId, edgeHashFieldPrefix);
    }

    public List<String> getFields(String redisKeyForPartyId, String prefix) {
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        Map<Object, Object> entries = hashOps.entries(redisKeyForPartyId);
        List<String> fields = new ArrayList<>();
        for (Map.Entry<Object, Object> entry : entries.entrySet()) {
            Object field = entry.getKey();
            if (!(field instanceof String)) {
                continue;
            }
            String fieldName = (String) field;
            if (!(fieldName.startsWith(prefix))) {
                continue;
            }
            fields.add(fieldName);
        }
        return fields;
    }

    private String formatRedisKey(String partyId) {
        return String.format(RedisKeyConstant.GRAPH_EXPLORE, partyId);
    }


}
